
using System;
using System.Collections.Generic;

namespace Ack
{		
	public class SnesDecoder
	{
		public static List<byte> Decode(ByteSupplier previous)
		{
			List<byte> output = new List<byte>();
			
			byte[] commonPatternA = new byte[8];
			byte[] commonPatternB = new byte[8];
			
			for(int i = 0; i < 8; ++i)
			{
				commonPatternA[i] = previous.ReadByte();
			}
			
			for(int i = 0; i < 8; ++i)
			{
				commonPatternB[i] = previous.ReadByte();
			}

			while(previous.HasMoreBytes())
			{
				byte groupHeader = previous.ReadByte();
				byte groupHeader2 = previous.ReadByte();
				byte groupHeader3 = previous.ReadByte();
				//int groupHeader = (groupHeader1 << 16) | (groupHeader2 << 8) | groupHeader3;
				
				// Now decode eight patterns.
				for(int pi = 0; pi < 8; ++pi)
				{
					int patternMode = (((groupHeader >> (7-pi)) & 1) << 2) | (((groupHeader2 >> (7-pi)) & 1) << 1) | ((groupHeader3 >> (7-pi)) & 1);
					
					switch(patternMode)
					{
						case 0: // Raw
						{
							if(!previous.HasMoreBytes())
								break;
							
							for(int ri = 0; ri < 8; ++ri)
							{
								output.Add(previous.ReadByte());
							}
						
							break;
						}
						case 1: // RCR
						{
							if(!previous.HasMoreBytes())
								break;
							
							// There's a one byte header, get it.
							byte rcrHeader = previous.ReadByte();

							// First row is always stored, rows 1-7 can be repeated from previous row.
							byte[] pattern = new byte[8];
							pattern[0] = previous.ReadByte();
							if((rcrHeader & 0x40)!=0) pattern[1] = previous.ReadByte(); else pattern[1] = pattern[0];
							if((rcrHeader & 0x20)!=0) pattern[2] = previous.ReadByte(); else pattern[2] = pattern[1];
							if((rcrHeader & 0x10)!=0) pattern[3] = previous.ReadByte(); else pattern[3] = pattern[2];
							if((rcrHeader & 0x08)!=0) pattern[4] = previous.ReadByte(); else pattern[4] = pattern[3];
							if((rcrHeader & 0x04)!=0) pattern[5] = previous.ReadByte(); else pattern[5] = pattern[4];
							if((rcrHeader & 0x02)!=0) pattern[6] = previous.ReadByte(); else pattern[6] = pattern[5];
							if((rcrHeader & 0x01)!=0) pattern[7] = previous.ReadByte(); else pattern[7] = pattern[6];
							
							// If bit 7 is set in the header, the repeats were column-oriented.
							if((rcrHeader & 0x80)!=0)
								pattern = GetTransposed8x8(pattern);

							// And write the pattern.
							output.AddRange(pattern);
						
							break;
						}
						case 2: // Common pattern A
						{
							output.AddRange(commonPatternA);
							break;
						}
						case 3: // Common pattern B
						{
							output.AddRange(commonPatternB);
							break;
						}
						case 4: // Same as last pattern
						{
							output.AddRange(output.GetRange(output.Count-8,8));
							break;
						}
						/*case 5: // Same as two patterns back
						{
							output.AddRange(output.GetRange(output.Count-16,8));
							break;
						}*/
						case 5: // Same as last but inverted
						{
							List<byte> pattern = output.GetRange(output.Count-8,8);
							for(int i = 0; i < 8; ++i)
							{
								pattern[i] = (byte)~pattern[i];
							}
							output.AddRange(pattern);
							break;
						}
					    case 6: // RD
						case 7: // RD (y-flipped)
						{
							// There's a two byte header, get it.
							byte eorHeader1 = previous.ReadByte();
							byte eorHeader2 = previous.ReadByte();
						
						    int offset = ((eorHeader1 & 0x7F)+1)*8;
						
							List<byte> referencePattern = output.GetRange(output.Count-offset,8);
						
							byte[] pattern = new byte[8];

							bool flipx = (eorHeader1 & 0x80)!=0;
							bool flipy = patternMode == 7;
						
							int bitmask = 0x80;
							for(int i = 0; i < 8; ++i, bitmask>>=1)
								if((eorHeader2 & bitmask)!=0) 
								{
									pattern[i] = previous.ReadByte(); 
								}
								else
								{
									pattern[i] = (byte)(referencePattern[flipy?7-i:i]);
							if(flipx) pattern[i] = Misc.ReverseByte(pattern[i]);
								}

							output.AddRange(pattern);
							break;
						}
					}
				}
			}
			
			return output;
		}

		public static byte[] GetTransposed8x8(byte[] input)
		{
			byte[] output = new byte[8];
			for(int y = 7; y >= 0; --y)
			{
				byte outRow = 0;
				outRow |= (byte)(((input[0] >> y) & 1) << 7);
				outRow |= (byte)(((input[1] >> y) & 1) << 6);
				outRow |= (byte)(((input[2] >> y) & 1) << 5);
				outRow |= (byte)(((input[3] >> y) & 1) << 4);
				outRow |= (byte)(((input[4] >> y) & 1) << 3);
				outRow |= (byte)(((input[5] >> y) & 1) << 2);
				outRow |= (byte)(((input[6] >> y) & 1) << 1);
				outRow |= (byte)(((input[7] >> y) & 1) << 0);
				output[7-y] = outRow;
			}
			
			return output;
		}
	}
}
